package org.fin.framework.ioc.factory;

import org.fin.framework.ioc.beans.BeanDefinition;
import org.fin.framework.ioc.beans.BeanInterceptor;
import org.fin.framework.ioc.beans.DependencyDefinition;
import org.fin.framework.ioc.exception.BeanInitializeException;
import org.fin.framework.ioc.exception.NoSuchBeanDefinitionException;
import org.fin.framework.ioc.exception.NoUniqueBeanDefinitionException;

import java.lang.reflect.*;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

public class DefaultBeanFactory implements BeanFactory {

  /** bean id对应的bean实例 */
  private Map<String, Object> singletons = new ConcurrentHashMap<>(64);

  /** class类型对应的一个或多个bean id，在读取bean信息时完成初始化 */
  private Map<Class<?>, Set<String>> beanNamesByType = new ConcurrentHashMap<>(32);

  /** bean id对应bean信息，在读取bean信息时完成初始化 */
  private Map<String, BeanDefinition> beanDefinitions = new ConcurrentHashMap<>(64);

  private List<BeanInterceptor> beanInterceptors = new ArrayList<>(16);

  /** 未初始化的bean id，在读取bean信息时完成初始化 */
  private List<String> uninitializedBeanNames = Collections.synchronizedList(new ArrayList<>(64));

  /** 提前暴露的bean的缓存 */
  private Map<String, Object> exposedBeanCache = new ConcurrentHashMap<>(64);

  /** 正在实例化的bean池，主要用来检测构造器循环依赖 */
  private Map<String, BeanDefinition> inCreationBeans = new ConcurrentHashMap<>(64);


  @Override
  public Object getBean(String id) {
    Object bean = singletons.get(id);
    if (bean == null) {
      throw new NoSuchBeanDefinitionException(id);
    }
    return bean;
  }

  /**
   * FIXME: 如果用实现类获取JDK代理的对象会抛错，因为JDK代理是实现了接口，跟实现类是兄弟关系
   */
  @SuppressWarnings("unchecked")
  @Override
  public <T> T getBean(String id, Class<T> type) {
    Object bean = getBean(id);

    if (!type.isAssignableFrom(bean.getClass())) {
      throw new NoSuchBeanDefinitionException("bean id: [" + id + "] 不匹配类型: [" + type.getName() + "]");
    }
    return (T) bean;
  }

  @Override
  public <T> T getBean(Class<T> type) {
    Set<String> beanNames = beanNamesByType.get(type);
    if (beanNames == null || beanNames.size() == 0) {
      throw new NoSuchBeanDefinitionException(type.getName());
    }

    if (beanNames.size() > 1) {
      throw new NoUniqueBeanDefinitionException(type.getName() + ": 有" + beanNames.size() + "个bean匹配此类型");
    }

    return getBean(beanNames.iterator().next(), type);
  }

  /**
   * 创建并实例化所有BeanInterceptor，要注意的是，被BeanInterceptor依赖的Bean和依赖链上的所有Bean，
   * 都无法应用BeanInterceptor的方法
   */
  public void registerBeanInterceptors() {
    List<String> beanNames = new ArrayList<>(this.uninitializedBeanNames);
    for (String beanName : beanNames) {
      BeanDefinition bd = beanDefinitions.get(beanName);
      if (BeanInterceptor.class.isAssignableFrom(bd.getType())) {
        resolveBean(bd);
      }
    }

    // 确保没有遗漏
    beanNames = new ArrayList<>(this.uninitializedBeanNames);
    for (String beanName : beanNames) {
      BeanDefinition bd = beanDefinitions.get(beanName);
      if (BeanInterceptor.class.isAssignableFrom(bd.getType())) {
        registerBeanInterceptors();
        break;
      }
    }
  }


  /**
   * 根据{@link #beanDefinitions}的bean信息，实例化所有的普通bean
   */
  public void instantiateBeans() {
    List<String> beanNames = new ArrayList<>(this.uninitializedBeanNames);
    for (String beanName : beanNames) {
      resolveBean(beanDefinitions.get(beanName));
    }

    // 确保没有遗漏bean
    while (this.uninitializedBeanNames.size() > 0) {
      instantiateBeans();
    }
  }

  private void resolveBean(BeanDefinition bd)
  {
    initializeBean(bd);
  }

  /**
   * Bean的初始化流程
   * @param bd    bean的信息
   * @return      完成初始化的bean实例
   */
  private Object initializeBean(BeanDefinition bd) {
    // 如果单例池中已经存在，代表已经完成初始化，直接返回
    Object bean = singletons.get(bd.getId());
    if (bean != null) {
      return bean;
    }

    // 如果发现自己已经在正在创建池中，代表是循环依赖，报错
    bean = inCreationBeans.get(bd.getId());
    if (bean != null) {
      throw new BeanInitializeException("构造器循环依赖");
    }

    // 如果已经在暴露bean池中，代表已经实例化，直接进入依赖注入流程
    // 如果不存在的话，执行bean实例化流程
    bean = exposedBeanCache.get(bd.getId());
    if(bean == null) {
      if (bd.isAbstract()) {
        throw new BeanInitializeException("\"" + bd.getType().getName() + "\"是一个抽象类或者接口，不能被实例化");
      }

      // 进入实例化bean流程，放入正在创建池中
      inCreationBeans.put(bd.getId(), bd);

      // 判断是否使用构造器注入，如果是，使用指定的构造器注入实例化Bean
      // 其他方式的依赖注入并未处理
      DependencyDefinition dd = isConstructorInjectionAndGet(bd);
      if (dd != null) {
        resolveConstructorInjectionBean(bd, dd);
      }

      else {
        try {
          Constructor<?> constrcuctor = bd.getType().getDeclaredConstructor();
          constrcuctor.setAccessible(true);
          bean = constrcuctor.newInstance();

          bd.setBean(bean);
          exposedBeanCache.put(bd.getId(), bean);
          inCreationBeans.remove(bd.getId());
        }

        catch (NoSuchMethodException e) {
          throw new BeanInitializeException(bd.getType().getName() + " 没有无参构造");
        }
        catch (IllegalAccessException e) {
          // 理论上不应该发生
          throw new BeanInitializeException(e);
        }
        catch (InstantiationException e) {
          throw new BeanInitializeException(bd.getType().getName() + " 是一个抽象类");
        }
        catch (InvocationTargetException e) {
          throw new BeanInitializeException(bd.getType().getName() + " 实例化时构造器抛出了一个错误", e);
        }
      }
    }

    // bean实例化后，执行其他初始化流程

    // 处理构造器以外方式的依赖注入
    resolveBeanInjection(bd);

    // 执行BeanInterceptor
    invokeInterceptors(bd);

    // bean初始化收尾工作
    afterBeanInitialization(bd);

    // bean对象可能被代理过，这里必须以BeanDefinition里面的为准
    return bd.getBean();
  }

  /**
   * 处理bean的依赖注入
   */
  private void resolveBeanInjection(BeanDefinition bd) {
    List<DependencyDefinition> dds = bd.getDependencyDefinitions();
    Object bean = bd.getBean();

    for (DependencyDefinition dd : dds) {
      Member injectTarget = dd.getInjectTarget();
      if (injectTarget.getClass() == Field.class) {
        injectToField((Field) injectTarget, dd, bean);
      }
      if (injectTarget.getClass() == Method.class) {
        injectToMethod((Method) injectTarget, dd, bean);
      }
    }
  }

  /**
   * 执行BeanInterceptor
   */
  private void invokeInterceptors(BeanDefinition bd) {
    Object bean = bd.getBean();

    if (!(bean instanceof BeanInterceptor)) {
      for (BeanInterceptor bi : beanInterceptors) {
        Object current = bi.intercept(bd.getId(), bean);
        // 如果返回null，则bean不变
        if (current != null) {
         bean = current;
        }
      }
      bd.setBean(bean);
    }
  }

  /**
   * bean初始化完成后做的收尾工作
   */
  private void afterBeanInitialization(BeanDefinition bd) {
    Object bean = bd.getBean();
    // 将初始化完成的bean添加到单例bean池
    singletons.put(bd.getId(), bean);
    // 清除未初始化的bean缓存
    uninitializedBeanNames.remove(bd.getId());
    // 清除提前暴露的bean
    exposedBeanCache.remove(bd.getId());

    if (bean instanceof BeanInterceptor) {
      beanInterceptors.add((BeanInterceptor) bean);
    }
  }

  /**
   * 判断是否使用构造器注入，如果是，返回构造器对象，否则返回null
   */
  private DependencyDefinition isConstructorInjectionAndGet(BeanDefinition bd) {
    for (DependencyDefinition dd :  bd.getDependencyDefinitions()) {
      if (dd.getInjectTarget().getClass() == Constructor.class) {
        return dd;
      }
    }

    return null;
  }

  /**
   * 处理构造器注入
   */
  private void resolveConstructorInjectionBean(BeanDefinition bd, DependencyDefinition dd) {
    Constructor<?> constructor = (Constructor<?>) dd.getInjectTarget();

    Class<?>[] parameterTypes = constructor.getParameterTypes();
    if (parameterTypes.length == 0) {
      throw new BeanInitializeException("注入的构造器没有参数: " +
          constructor.getDeclaringClass().getName() + "." + constructor.getName());
    }else {
      Object[] args = new Object[parameterTypes.length];
      // 根据方法的参数类型获取Bean实例
      for (int i = 0; i < parameterTypes.length; i++) {
        Class<?> parameterType = parameterTypes[i];
        String beanName = getBeanName(parameterType);
        dd.addInjectBeanId(beanName);

        // 从缓存中获取bean实例，如果没有，进行创建bean后进行注入
        // 如果有，则直接进行注入
        Object injectBean = getSingleton(beanName);
        if (injectBean == null) {
          injectBean = initializeBean(beanDefinitions.get(beanName));
        }

        // 给注入的参数赋值
        args[i] = injectBean;

      }

      try {
        // 设置暴力访问
        constructor.setAccessible(true);
        Object bean = constructor.newInstance(args);
        exposedBeanCache.put(bd.getId(), bean);
        inCreationBeans.remove(bd.getId());

      } catch (Exception e) {
        throw new BeanInitializeException(constructor.getDeclaringClass().getName() + "."
            + constructor.getName() + " 抛出了一个错误", e);
      }
    }
  }

  /**
   * 注入到属性
   */
  private void injectToField(Field injectTarget, DependencyDefinition dd, Object bean) {

    Class<?> fieldType = injectTarget.getType();
    String beanName = getBeanName(fieldType);
    dd.addInjectBeanId(beanName);

    // 从缓存中获取bean实例，如果没有，进行创建bean后进行注入
    // 如果有，则直接进行注入
    Object injectBean = getSingleton(beanName);
    if (injectBean == null) {
      BeanDefinition bd = beanDefinitions.get(beanName);
      injectBean = initializeBean(bd);
    }

    // 注入
    try {
      injectTarget.setAccessible(true);
      injectTarget.set(bean, injectBean);
    } catch (IllegalAccessException e) {
      // 理论上不应该发生
      throw new BeanInitializeException(e);
    }
  }

  /**
   * 注入到方法
   */
  private void injectToMethod(Method injectTarget, DependencyDefinition dd, Object bean) {

    Class<?>[] parameterTypes = injectTarget.getParameterTypes();
    if (parameterTypes.length == 0) {
      throw new BeanInitializeException("注入的方法没有参数: " +
          injectTarget.getDeclaringClass().getName() + "." + injectTarget.getName());
    }else {
      Object[] args = new Object[parameterTypes.length];
      // 根据方法的参数类型获取Bean实例
      for (int i = 0; i < parameterTypes.length; i++) {
        Class<?> parameterType = parameterTypes[i];
        String beanName = getBeanName(parameterType);
        dd.addInjectBeanId(beanName);

        // 从缓存中获取bean实例，如果没有，进行创建bean后进行注入
        // 如果有，则直接进行注入
        Object injectBean = getSingleton(beanName);
        if (injectBean == null) {
          BeanDefinition bd = beanDefinitions.get(beanName);
          injectBean = initializeBean(bd);
        }

        // 给注入的参数赋值
        args[i] = injectBean;

      }

      try {
        // 设置暴力访问
        injectTarget.setAccessible(true);
        injectTarget.invoke(bean, args);
      } catch (Exception e) {
        throw new BeanInitializeException(injectTarget.getDeclaringClass().getName() + "."
            + injectTarget.getName() + " 抛出了一个错误", e);
      }
    }
  }

  /**
   * 根据bean类型获取bean id，不支持一个类型有多个bean实例
   */
  private String getBeanName(Class<?> parameterType) {
    Set<String> beanNames = beanNamesByType.get(parameterType);
    if (beanNames == null) {
      throw new BeanInitializeException("此类型对应的bean不存在: " + parameterType.getName());
    } else if (beanNames.size() > 1) {
      throw new BeanInitializeException("此类型对应的bean存在多个: " + parameterType.getName());
    }

    return beanNames.iterator().next();
  }


  /**
   * 从缓存中获取bean，如果没有返回null
   */
  private Object getSingleton(String beanName) {
    Object bean = singletons.get(beanName);
    if (bean == null) {
      bean = exposedBeanCache.get(beanName);
    }
    return bean;
  }


  public Map<String, Object> getSingletons() {
    return singletons;
  }

  public Map<Class<?>, Set<String>> getBeanNamesByType() {
    return beanNamesByType;
  }

  public Map<String, BeanDefinition> getBeanDefinitions() {
    return beanDefinitions;
  }

  public List<String> getUninitializedBeanNames() {
    return uninitializedBeanNames;
  }

  public Map<String, Object> getExposedBeanCache() {
    return exposedBeanCache;
  }


}
